﻿/*
 * Copyright (C) 2012, Jesus Requejo <jesus.requejo@gmail.com> 
 * Licensed under Microsoft Public License (Ms-PL)
 * http://www.microsoft.com/en-us/openness/licenses.aspx#MPL
*/

namespace LoaderServiceManager {

   using System;
   using System.Collections.Generic;
   using System.ComponentModel;
   using System.Drawing;
   using System.Linq;
   using System.Windows.Forms;
   using Infrastructure;
   using LoaderServiceManager.AppLogic;
   using WindowsFormsInfrastructure;

   public partial class MainForm : Form {

      private BindingList<Infrastructure.ApplicationEntry> m_AtLogonBindingList;
      private BindingList<Infrastructure.ApplicationEntry> m_OnDemandBindingList;
      private ApplicationServices m_applicationServices = new ApplicationServices();
      private TrayHelper Tray { get; set; }
      private MessageWindow MessageWindow { get; set; }
      private PermissionsHelper Permissions { get; set; }
      private bool InitializationSuccesful { get; set; }

      public MainForm() {
         InitializeComponent();
         InitializeProperties();
         SetUpEvents();
         InitializeMessageWindow();
         InitializeMainWindow();
         InitializeTrayContextMenu();
         //InitializationSuccesful = true;
         InitializationSuccesful = SetUpDataBinding();
         FocusDataGrid(tabControl.SelectedTab);
      }

      private void InitializeMainWindow() {
         string[] args = Environment.GetCommandLineArgs();
         if (args.Length > 1 && string.Equals(args[1], MagicStrings.StartMinimizedCommandArgument)) {
            Tray.SendToTray();
         }
      }

      private void InitializeTrayContextMenu() {
         notifyIcon1.ContextMenuStrip = contextMenuStrip1;
         var uacShieldBitmap = UacIntegration.GetSmallShieldBitmap();
         contextMenuStrip1.Items.Add("Quit");
         contextMenuStrip1.Items[0].Click += (o, a) => { Close(); };
      }

      /// <summary>
      /// Necessary because Application.Exit() won't work in the constructor
      /// </summary>
      /// <param name="e"></param>
      protected override void OnLoad(EventArgs e) {
         if (!InitializationSuccesful) {
            Application.Exit();
         }
      }

      private void InitializeProperties() {
         Tray = new TrayHelper(this);
         Permissions = new PermissionsHelper(this);
      }

      public void RestoreFromTrayAndBringToFront() {
         if (WindowState == FormWindowState.Minimized) {
            Tray.RestoreFromTray();
         }
      }

      private void InitializeMessageWindow() {
         string message = MagicStrings.BringServiceManagerToFrontMessage;
         string windowName = MagicStrings.ManagerMessageWindowName;
         MessageWindow = new MessageWindow(this, windowName, message, RestoreFromTrayAndBringToFront);
      }


      private bool SetUpDataBinding() {
         try {
            var applications = m_applicationServices.LoadApplicationList();
            SetUpDataBindingHelper(applications, ref m_AtLogonBindingList, atLogonDataGrid, true);
            SetUpDataBindingHelper(applications, ref m_OnDemandBindingList, onDemandDataGrid, false);
            WarnOfDuplicatedPaths();
            return true;
         }
         catch (Exception) {
            MessageBox.Show("I couldn't have access to the applications list. Program will terminate now.",
               "Fatal Error",
               MessageBoxButtons.OK,
               MessageBoxIcon.Error
            );
            Application.Exit();
            return false;
         }
      }

      private void WarnOfDuplicatedPaths() {
         SetDuplicatedPathsWarningHelper(atlogonWarningLabel, m_AtLogonBindingList);
         SetDuplicatedPathsWarningHelper(ondemandWarningLabel, m_OnDemandBindingList);
      }

      private void SetDuplicatedPathsWarningHelper(Label warningLabel, BindingList<ApplicationEntry> list) {
         bool duplicatesFound = list.GroupBy(a => a.Path.ToLowerInvariant()).Any(group => group.Count() > 1);
         warningLabel.Visible = duplicatesFound;
      }

      private void SetUpDataBindingHelper(IList<Infrastructure.ApplicationEntry> applications, ref BindingList<Infrastructure.ApplicationEntry> bindingList, DataGridView dataGrid, bool launchAtLogon) {
         var filteredApplications = applications.Where(x => x.LaunchAtLogon == launchAtLogon).OrderBy(app => app.Name).ToList();
         dataGrid.AllowUserToAddRows = true;
         bindingList = new BindingList<Infrastructure.ApplicationEntry>(filteredApplications);
         bindingList.AllowNew = false;
         bindingList.AllowRemove = true;
         dataGrid.DataSource = bindingList;
         dataGrid.ReadOnly = false;
         dataGrid.RowHeadersVisible = false;
         dataGrid.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None;
         dataGrid.BackgroundColor = System.Drawing.SystemColors.Control; // a bit of a hack, but effective. See http://stackoverflow.com/questions/2122985/removing-the-empty-gray-space-in-datagrid-in-c                  
      }

      private void SetUpEvents() {
         SetUpSendAndRestoreToTrayEvents();
         this.atLogonDataGrid.DataBindingComplete += atLogonDataGrid_DataBindingComplete;
         this.onDemandDataGrid.DataBindingComplete += onDemandDataGrid_DataBindingComplete;
         this.onDemandDataGrid.CellContentDoubleClick += (o, e) => {
            var row = onDemandDataGrid.Rows[e.RowIndex];
            LaunchAppInRow(row);
         };
         this.tabControl.Selected += (o, e) => {
            if (tabControl.SelectedTab == permissionsTab) {
               Permissions.RefreshPermissionsTab();
            }
            else {
               FocusDataGrid(e.TabPage);
            }
         };
      }

      private void HighlightDuplicatedEntries(DataGridView dataGrid, BindingList<ApplicationEntry> list) {
         var duplicatedEntries = list.
             GroupBy(a => a.Path.ToLowerInvariant()).
             Where(group => group.Count() > 1).
             SelectMany(gr => gr.Select(ap => ap.Id));

         var highlightedStyle = new DataGridViewCellStyle();
         highlightedStyle.BackColor = Color.LightBlue;
         foreach (DataGridViewRow row in dataGrid.Rows) {
            var id = GetApplicationId(row.Cells[1]);
            if (duplicatedEntries.Contains(id)) {
               foreach (DataGridViewCell cell in row.Cells) {
                  cell.Style = highlightedStyle;
               }
            }
         }
         dataGrid.Refresh();
      }

      private void SetUpSendAndRestoreToTrayEvents() {
         this.Resize += Tray.Form1_Resize;
         this.notifyIcon1.MouseClick += Tray.notifyIcon1_MouseClickedInTrayIcon;
      }

      void atLogonDataGrid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) {
         var dataGrid = atLogonDataGrid;
         string idColumnName = Common.IdProperty;
         string launchAtLogonColumnName = Common.LaunchAtLogonProperty;
         HideDatagridColumns(dataGrid, new List<string>() { idColumnName, launchAtLogonColumnName });
         SetMinimumNameColumnWidth(dataGrid);
         dataGrid.AutoResizeColumns();
         EnableOrDisableEditingLinks(atLogonDataGrid, changeAtLogonEntryLink, removeAtLogonEntryLink);
         HighlightDuplicatedEntries(atLogonDataGrid, m_AtLogonBindingList);
      }

      void onDemandDataGrid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) {
         var dataGrid = onDemandDataGrid;
         string idColumnName = Common.IdProperty;
         string launchAtLogonColumnName = Common.LaunchAtLogonProperty;
         string enabledColumnName = Common.EnabledProperty;
         HideDatagridColumns(dataGrid, new List<string>() { idColumnName, launchAtLogonColumnName, enabledColumnName });
         SetMinimumNameColumnWidth(dataGrid);
         dataGrid.AutoResizeColumns();
         EnableOrDisableEditingLinks(onDemandDataGrid, changeOnDemandEntryLink, removeOnDemandEntryLink, launchAppButton);
         HighlightDuplicatedEntries(onDemandDataGrid, m_OnDemandBindingList);
      }

      private void EnableOrDisableEditingLinks(DataGridView dataGrid, params Control[] controls) {
         var bindingList = dataGrid.DataSource as IBindingList;
         bool enable;
         enable = (bindingList != null && bindingList.Count != 0) ? true : false;
         foreach (var control in controls) {
            control.Enabled = enable;
         }
      }

      private void SetMinimumNameColumnWidth(DataGridView dataGrid) {
         var col = dataGrid.Columns["Name"];
         if (col != null) {
            col.MinimumWidth = 90;
         }
      }

      private void HideDatagridColumns(DataGridView dataGrid, IList<string> columns) {
         foreach (var columnName in columns) {
            dataGrid.Columns[columnName].Visible = false;
         }
      }

      private void addAtLogonApplicationLink_Clicked(object sender, LinkLabelLinkClickedEventArgs e) {
         bool launchAtLogon = true;
         AddOrEditApplication addApplicationDialog = new AddOrEditApplication();
         AddOrEditApplicationHelper(addApplicationDialog, launchAtLogon, AddNewApplication);
      }

      private void addOnDemandApplicationLink_Clicked(object sender, LinkLabelLinkClickedEventArgs e) {
         bool launchAtLogon = false;
         AddOrEditApplication addApplicationDialog = new AddOrEditApplication();
         addApplicationDialog.HideEnabledCheckBox();
         AddOrEditApplicationHelper(addApplicationDialog, launchAtLogon, AddNewApplication);
      }

      private void AddOrEditApplicationHelper(AddOrEditApplication dialog, bool launchAtLogon, Action<string, string, bool, bool> processAppDetails) {

         bool notValidOrCancelled = true;
         // FIXME: not the most beautiful code ever written :(
         while (notValidOrCancelled) {
            dialog.ShowDialog(this);
            if (dialog.DialogResult == System.Windows.Forms.DialogResult.OK) {
               string name = dialog.AppName;
               string path = dialog.AppPath;
               bool enabled = dialog.AppEnabled;

               ValidationResult validationResult = m_applicationServices.ValidateApplication(name, path);
               if (!validationResult.IsValid) {
                  bool proceed = AskIfValidationErrorIsAcceptable(validationResult);
                  if (proceed) {
                     notValidOrCancelled = false;
                     processAppDetails(name, path, enabled, launchAtLogon);
                  }
               }
               else {
                  notValidOrCancelled = false;
                  processAppDetails(name, path, enabled, launchAtLogon);
               }
            }
            else {
               notValidOrCancelled = false;
            }
         }
      }

      private bool AskIfValidationErrorIsAcceptable(ValidationResult validationResult) {
         bool proceed = false;
         if (validationResult.Errors.ContainsKey(ValidationCategories.Name)) {
            MessageBox.Show(validationResult.Errors[ValidationCategories.Name],
               "Validation error",
               MessageBoxButtons.OK,
               MessageBoxIcon.Warning
            );
         }
         else if (validationResult.Errors.ContainsKey(ValidationCategories.Path)) {
            MessageBox.Show(validationResult.Errors[ValidationCategories.Path],
               "Validation error",
               MessageBoxButtons.OK,
               MessageBoxIcon.Warning
            );
         }
         else if (validationResult.Errors.ContainsKey(ValidationCategories.Accessibility)) {
            var dialogResult = MessageBox.Show("I couldn't access the application at the specified path.\nContinue anyway and add the entry to the list?",
               "Continue anyway?", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (dialogResult == System.Windows.Forms.DialogResult.Yes) {
               proceed = true;
            }
         }
         else {
            throw new Exception("An error was found in a property that is not Name or Location. This shouldn't happen");
         }
         return proceed;
      }

      private void AddNewApplication(string name, string path, bool enabled, bool launchOnLogon) {
         try {
            m_applicationServices.AddNewApplication(name, path, enabled, launchOnLogon);
         }
         catch (Exception) {
            MessageBox.Show("I'm really sorry, I couldn't save the new application entry. Trying to load existing entries",
               "Error",
               MessageBoxButtons.OK,
               MessageBoxIcon.Warning
            );
         }
         SetUpDataBinding();
      }

      private void addPredefinedLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
         IList<ApplicationEntry> appList = m_applicationServices.LoadPredefinedApps();
         var dialog = new PredefinedAppsCheckList(appList);
         dialog.ShowDialog(this);
         if (dialog.SelectedApps.Count > 0) {
            AddOnDemandApplicationBatch(dialog.SelectedApps);
         }
      }

      private void AddOnDemandApplicationBatch(IEnumerable<ApplicationEntry> apps) {
         try {
            foreach (var app in apps) {
               var name = app.Name;
               var path = app.Path;
               m_applicationServices.AddNewApplication(name, path, enabled: true, launchAtLogon: false);
            }
         }
         catch (Exception) {
            MessageBox.Show("An error occurred while saving one or more applications.",
               "Error",
               MessageBoxButtons.OK,
               MessageBoxIcon.Warning
            );
         }
         finally {
            SetUpDataBinding();
         }
      }

      private void dataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e) {
         var index = e.RowIndex;
         var enabledColumnName = Common.EnabledProperty;
         var dataGrid = sender as DataGridView;
         var row = dataGrid.Rows[index];
         var appEnabled = (bool)row.Cells[enabledColumnName].Value;
         if (!appEnabled) {
            DisableGridRow(row, dataGrid);
         }
         else {
            EnableGridRow(row, dataGrid);
         }
      }

      private void DisableGridRow(DataGridViewRow row, DataGridView dataGrid) {
         var style = new DataGridViewCellStyle();
         style.ForeColor = Color.Gray;
         row.DefaultCellStyle = style;
      }

      private void EnableGridRow(DataGridViewRow row, DataGridView dataGrid) {
         var style = new DataGridViewCellStyle();
         row.DefaultCellStyle = style;
      }

      private void atLogonDataGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
         // see http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.cellclick.aspxs
         var dataGrid = sender as DataGridView;

         if (dataGrid.IsCurrentRowDirty && dataGrid.CurrentCell is DataGridViewCheckBoxCell) {
            dataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
         }
      }

      private void atLogonDataGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
         var dataGrid = sender as DataGridView;

         var idCell = dataGrid.Rows[e.RowIndex].Cells[Common.IdProperty];
         int id = (int)idCell.Value;
         var nameCell = dataGrid.Rows[e.RowIndex].Cells[Common.NameProperty];
         string name = (string)nameCell.Value;
         var pathCell = dataGrid.Rows[e.RowIndex].Cells[Common.PathProperty];
         string path = (string)pathCell.Value;
         var enabledCell = dataGrid.Rows[e.RowIndex].Cells[Common.EnabledProperty];
         bool enabled = (bool)enabledCell.Value;

         bool launchOnLogon = true;
         UpdateApplication(id, name, path, enabled, launchOnLogon);
         SetUpDataBinding();
      }

      private void onDemandDataGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
         var dataGrid = sender as DataGridView;

         var idCell = dataGrid.Rows[e.RowIndex].Cells[Common.IdProperty];
         int id = (int)idCell.Value;
         var nameCell = dataGrid.Rows[e.RowIndex].Cells[Common.NameProperty];
         string name = (string)nameCell.Value;
         var pathCell = dataGrid.Rows[e.RowIndex].Cells[Common.PathProperty];
         string path = (string)pathCell.Value;

         bool enabled = true;
         bool launchOnLogon = false;
         UpdateApplication(id, name, path, enabled, launchOnLogon);
         SetUpDataBinding();
      }

      private void UpdateApplication(int id, string name, string path, bool enabled, bool launchOnLogon) {
         m_applicationServices.UpdateApplication(id, name, path, enabled, launchOnLogon);
      }

      private void atLogonDataGrid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) {
         var dataGrid = sender as DataGridView;
         ValidateCellHelper(e, dataGrid);
      }

      private void onDemandDataGrid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) {
         var dataGrid = sender as DataGridView;
         ValidateCellHelper(e, dataGrid);
      }

      private void ValidateCellHelper(DataGridViewCellValidatingEventArgs e, DataGridView dataGrid) {
         if (dataGrid.IsCurrentCellInEditMode && !(dataGrid.CurrentCell is DataGridViewCheckBoxCell)) {
            var row = dataGrid.Rows[e.RowIndex];
            var cell = dataGrid.Rows[e.RowIndex].Cells[e.ColumnIndex];
            var oldValue = cell.Value;
            var newValue = e.FormattedValue;

            var nameCell = row.Cells[Common.NameProperty];
            var pathCell = row.Cells[Common.PathProperty];
            string nameCellValue;
            string pathCellValue;
            if (e.ColumnIndex == nameCell.ColumnIndex) {
               nameCellValue = newValue as string;
               pathCellValue = pathCell.Value as string;
            }
            else {
               nameCellValue = nameCell.Value as string;
               pathCellValue = newValue as string;
            }

            ValidationResult validation = m_applicationServices.ValidateApplication(nameCellValue, pathCellValue);
            if (!validation.IsValid) {
               var proceedAnyway = AskIfValidationErrorIsAcceptable(validation);
               if (!proceedAnyway) {
                  // restore old value
                  dataGrid.EditingControl.Text = oldValue as string;
               }
            }
         }
      }

      private void editAppDetailsAtLogonApplicationLink_Clicked(object sender, LinkLabelLinkClickedEventArgs e) {
         var dataGrid = atLogonDataGrid;
         if (dataGrid.CurrentCell == null)
            return;

         bool launchAtLogon = true;
         var currentRow = dataGrid.CurrentRow;

         AddOrEditApplication dialog = new AddOrEditApplication();
         dialog.AppEnabled = (bool)currentRow.Cells[Common.EnabledProperty].Value;
         EditAppDetailsHelper(dataGrid, currentRow, dialog, launchAtLogon);
      }

      private void editAppDetailsOnDemandApplicationLink_Clicked(object sender, LinkLabelLinkClickedEventArgs e) {
         var dataGrid = onDemandDataGrid;
         if (dataGrid.CurrentCell == null)
            return;

         bool launchAtLogon = true;
         var currentRow = dataGrid.CurrentRow;

         AddOrEditApplication dialog = new AddOrEditApplication();
         dialog.HideEnabledCheckBox();
         EditAppDetailsHelper(dataGrid, currentRow, dialog, launchAtLogon);
      }

      private void EditAppDetailsHelper(DataGridView dataGrid, DataGridViewRow row, AddOrEditApplication dialog, bool launchAtLogon) {

         var applicationId = GetApplicationId(dataGrid.CurrentCell);

         dialog.SetGroupBoxText("Edit application details");
         dialog.AppName = (string)row.Cells[Common.NameProperty].Value;
         dialog.AppPath = (string)row.Cells[Common.PathProperty].Value;

         AddOrEditApplicationHelper(dialog, launchAtLogon,
            (name, path, enabled, launch) => {
               UpdateApplication(applicationId, name, path, enabled, launch);
               UpdateDataGridRow(dataGrid.CurrentRow, name, path, enabled, launch);
            }
            );
      }

      private void UpdateDataGridRow(DataGridViewRow row, string name, string path, bool enabled, bool launchAtLogon) {
         row.Cells[Common.NameProperty].Value = name;
         row.Cells[Common.PathProperty].Value = path;
         row.Cells[Common.EnabledProperty].Value = enabled;
         row.Cells[Common.LaunchAtLogonProperty].Value = launchAtLogon;
      }

      private void removeAtLogonEntryLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
         var dataGrid = atLogonDataGrid;
         if (dataGrid.CurrentCell == null)
            return;

         RemoveAppHelper(dataGrid);
      }

      private void removeOnDemandEntryLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
         var dataGrid = onDemandDataGrid;
         if (dataGrid.CurrentCell == null)
            return;

         RemoveAppHelper(dataGrid);
      }

      private void RemoveAppHelper(DataGridView dataGrid) {
         var currentCell = dataGrid.CurrentCell;
         var appName = (string)currentCell.OwningRow.Cells[Common.NameProperty].Value;
         var message = string.Format("Remove application '{0}'?", appName);
         var dialogResult = MessageBox.Show(message, "Confirm entry removal", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
         if (dialogResult == System.Windows.Forms.DialogResult.Yes) {
            var appId = GetApplicationId(currentCell);
            m_applicationServices.RemoveApplication(appId);
            dataGrid.Rows.Remove(currentCell.OwningRow);
            SetUpDataBinding();
         }
      }

      private int GetApplicationId(DataGridViewCell cell) {
         var dataGrid = cell.DataGridView;
         var rowIndex = cell.RowIndex;
         return (int)dataGrid.Rows[rowIndex].Cells[Common.IdProperty].Value;
      }

      private void launchAppButton_Click(object sender, EventArgs e) {
         var dataGrid = onDemandDataGrid;
         var currentCell = dataGrid.CurrentCell;
         if (currentCell == null)
            return;
         var row = currentCell.OwningRow;
         LaunchAppInRow(row);
      }

      private void LaunchAppInRow(DataGridViewRow row) {
         var appPath = (string)row.Cells[Common.PathProperty].Value;
         try {
            m_applicationServices.RunProcess(appPath);

         }
         catch (Exception) {
            MessageBox.Show("The selected application could not be launched.",
               "Error",
               MessageBoxButtons.OK,
               MessageBoxIcon.Warning
            );
         }
      }

      private void FocusDataGrid(TabPage page) {
         var dataGrid = page.Controls.OfType<DataGridView>().FirstOrDefault();
         if (dataGrid != null) {
            dataGrid.Select();
         }
      }

      private class PermissionsHelper {

         private MainForm Form { get; set; }
         private bool IsDirty { get; set; }
         private PermissionsConfiguration Permissions { get; set; }

         public PermissionsHelper(MainForm form) {
            Form = form;
            IsDirty = true;
            Form.changeStartupPermissionsLink.Click += new EventHandler(changeStartupPermissionsLink_Click);
            Form.changeManagerPermissionsLink.Click += new EventHandler(changeManagerPermissionsLink_Click);
         }

         public void RefreshPermissionsTab() {
            if (IsDirty) {
               Permissions = Form.m_applicationServices.GetPermissionsConfiguration();
               UpdateStartupApplicationsStatus(Permissions);
               UpdateServiceManagerStatus(Permissions);
            }
            IsDirty = false;
         }

         private void UpdateStartupApplicationsStatus(PermissionsConfiguration permissions) {
            string status;
            switch (permissions.StartupPermissions) {
               case StartupPermissions.Everybody:
                  status = "Startup applications will be launched for every user at logon";
                  break;
               case StartupPermissions.Admins:
                  status = "Startup applications will be launched for administrators only";
                  break;
               case StartupPermissions.Authorized:
                  status = "Startup applications will be launched for authorized users only";
                  break;
               default:
                  throw new ArgumentException();
            }
            Form.startupApplicationsPermissionsLabel.Text = status;
         }

         private void UpdateServiceManagerStatus(PermissionsConfiguration permissions) {
            string status;
            switch (permissions.ServiceManagerPermissions) {
               case ServiceManagerPermissions.Nobody:
                  status = "The service manager won't start automatically";
                  break;
               case ServiceManagerPermissions.Admins:
                  status = "The service manager will start automatically for administrators only";
                  break;
               case ServiceManagerPermissions.Authorized:
                  status = "The service manager will start automatically for authorized users only";
                  break;
               default:
                  throw new ArgumentException();
            }
            Form.serviceManagerPermissionsLabel.Text = status;
         }

         void changeStartupPermissionsLink_Click(object sender, EventArgs e) {
            string groupBoxText = "Startup Applications";
            string labelText = "Startup applications will be launched for:";
            string radioButtonText = "Everybody";
            var copyList = new List<WindowsUser>(Permissions.AuthorizedStartupUsers);
            var usersList = new BindingList<WindowsUser>(copyList);

            var radioButtonTags = new List<string>() { 
               StartupPermissions.Everybody.ToString(),
               StartupPermissions.Admins.ToString(),
               StartupPermissions.Authorized.ToString()
            };
            string currentUserClass = Permissions.StartupPermissions.ToString();
            var permissionsDialog = ShowPermissionsDialog(groupBoxText, labelText, radioButtonText, usersList, radioButtonTags, currentUserClass);
            if (permissionsDialog.DialogResult == DialogResult.OK) {
               UpdateStartupPermissions(permissionsDialog, Permissions);
            }
         }

         void changeManagerPermissionsLink_Click(object sender, EventArgs e) {
            string groupBoxText = "Service Manager";
            string labelText = "Service Manager will be launched for:";
            string radioButtonText = "Nobody";
            var radioButtonTags = new List<string>() { 
               ServiceManagerPermissions.Nobody.ToString(),
               ServiceManagerPermissions.Admins.ToString(),
               ServiceManagerPermissions.Authorized.ToString()
            };
            string currentUserClass = Permissions.ServiceManagerPermissions.ToString();
            var copyList = new List<WindowsUser>(Permissions.AuthorizedServiceManagerUsers);
            var usersList = new BindingList<WindowsUser>(copyList);
            var permissionsDialog = ShowPermissionsDialog(groupBoxText, labelText, radioButtonText, usersList, radioButtonTags, currentUserClass);
            if (permissionsDialog.DialogResult == DialogResult.OK) {
               UpdateManagerPermissions(permissionsDialog, Permissions);
            }
         }

         private EditPermissions ShowPermissionsDialog(string groupBoxText, string labelText, string radioButtonText, BindingList<WindowsUser> usersList, List<string> radioTags, string currentUserClass) {
            if (IsDirty) {
               throw new InvalidOperationException();
            }
            var permissionsDialog = new EditPermissions(groupBoxText, labelText, radioButtonText, usersList, radioTags, currentUserClass, Form.m_applicationServices);
            permissionsDialog.ShowDialog();
            return permissionsDialog;
         }

         private void UpdateStartupPermissions(EditPermissions permissionsDialog, PermissionsConfiguration permissions) {
            UpdatePermissionsHelper(
               permissionsDialog,
               (tag) => (StartupPermissions)Enum.Parse(typeof(StartupPermissions), tag),
               (enumeration, userList) => {
                  Form.m_applicationServices.UpdateStartupPermissions((StartupPermissions)enumeration, userList, permissions);
               }
            );
         }

         private void UpdateManagerPermissions(EditPermissions permissionsDialog, PermissionsConfiguration permissions) {
            UpdatePermissionsHelper(
               permissionsDialog,
               (tag) => (ServiceManagerPermissions)Enum.Parse(typeof(ServiceManagerPermissions), tag),
               (enumeration, userList) => {
                  Form.m_applicationServices.UpdateServiceManagerPermissions((ServiceManagerPermissions)enumeration, userList, permissions);
               }
            );
         }

         private void UpdatePermissionsHelper(EditPermissions permissionsDialog, Func<string, Enum> stringToEnum, Action<Enum, IList<string>> updateFunction) {
            if (!permissionsDialog.IsDirty) {
               return;
            }
            IsDirty = true;
            try {
               var buttonTag = permissionsDialog.GetSelectedCheckBoxTag();
               Enum newUserClass = stringToEnum(buttonTag);
               var authorizedUsersList = permissionsDialog.GetUsersList().ToList();
               updateFunction(newUserClass, authorizedUsersList);
            }
            catch (Exception) {
               MessageBox.Show("I could not write the new permissions configuration.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            finally {
               RefreshPermissionsTab();
            }
         }
      }
   }
}
